package com.example.recommendation;

import com.example.util.DatabaseUtil;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 协同过滤推荐算法实现
 * 包括基于物品的协同过滤(Item-based CF)和基于用户的协同过滤(User-based CF)
 */
public class CollaborativeFiltering {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    /**
     * 基于物品的协同过滤推荐
     * @param targetItem 目标物品
     * @param topN 推荐数量
     * @return 推荐物品列表
     */
    public List<RecommendationItem> itemBasedRecommendation(String targetItem, int topN) {
        Map<String, Map<String, Double>> userItemMatrix = loadUserItemMatrix();
        Map<String, Double> itemSimilarities = calculateItemSimilarities(targetItem, userItemMatrix);
        
        return itemSimilarities.entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
                .limit(topN)
                .map(entry -> new RecommendationItem(entry.getKey(), entry.getValue(), "Item-Based"))
                .collect(Collectors.toList());
    }
    
    /**
     * 基于用户的协同过滤推荐
     * @param targetUser 目标用户
     * @param topN 推荐数量
     * @return 推荐物品列表
     */
    public List<RecommendationItem> userBasedRecommendation(String targetUser, int topN) {
        Map<String, Map<String, Double>> userItemMatrix = loadUserItemMatrix();
        Map<String, Double> userSimilarities = calculateUserSimilarities(targetUser, userItemMatrix);
        Map<String, Double> itemScores = new HashMap<>();
        
        // 基于相似用户计算物品得分
        for (Map.Entry<String, Double> similarUser : userSimilarities.entrySet()) {
            String userId = similarUser.getKey();
            double similarity = similarUser.getValue();
            
            if (userItemMatrix.containsKey(userId)) {
                for (Map.Entry<String, Double> item : userItemMatrix.get(userId).entrySet()) {
                    String itemId = item.getKey();
                    double rating = item.getValue();
                    
                    // 如果目标用户还没有对该物品评分
                    if (!userItemMatrix.containsKey(targetUser) || 
                        !userItemMatrix.get(targetUser).containsKey(itemId)) {
                        itemScores.put(itemId, itemScores.getOrDefault(itemId, 0.0) + similarity * rating);
                    }
                }
            }
        }
        
        return itemScores.entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
                .limit(topN)
                .map(entry -> new RecommendationItem(entry.getKey(), entry.getValue(), "User-Based"))
                .collect(Collectors.toList());
    }
    
    /**
     * 计算物品相似度
     */
    private Map<String, Double> calculateItemSimilarities(String targetItem, Map<String, Map<String, Double>> userItemMatrix) {
        Map<String, Double> similarities = new HashMap<>();
        Set<String> targetUsers = new HashSet<>();
        
        // 找到对目标物品有评分的用户
        for (Map.Entry<String, Map<String, Double>> user : userItemMatrix.entrySet()) {
            if (user.getValue().containsKey(targetItem)) {
                targetUsers.add(user.getKey());
            }
        }
        
        // 计算与其他物品的相似度
        Set<String> allItems = userItemMatrix.values().stream()
                .flatMap(items -> items.keySet().stream())
                .collect(Collectors.toSet());
        
        for (String item : allItems) {
            if (!item.equals(targetItem)) {
                double similarity = calculateCosineSimilarity(targetItem, item, userItemMatrix, targetUsers);
                if (similarity > 0) {
                    similarities.put(item, similarity);
                }
            }
        }
        
        return similarities;
    }
    
    /**
     * 计算用户相似度
     */
    private Map<String, Double> calculateUserSimilarities(String targetUser, Map<String, Map<String, Double>> userItemMatrix) {
        Map<String, Double> similarities = new HashMap<>();
        
        if (!userItemMatrix.containsKey(targetUser)) {
            return similarities;
        }
        
        Map<String, Double> targetUserRatings = userItemMatrix.get(targetUser);
        
        for (Map.Entry<String, Map<String, Double>> user : userItemMatrix.entrySet()) {
            String userId = user.getKey();
            if (!userId.equals(targetUser)) {
                double similarity = calculateUserCosineSimilarity(targetUserRatings, user.getValue());
                if (similarity > 0) {
                    similarities.put(userId, similarity);
                }
            }
        }
        
        return similarities;
    }
    
    /**
     * 计算余弦相似度（物品间）
     */
    private double calculateCosineSimilarity(String item1, String item2, 
                                           Map<String, Map<String, Double>> userItemMatrix, 
                                           Set<String> targetUsers) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;
        
        for (String user : targetUsers) {
            if (userItemMatrix.containsKey(user)) {
                Map<String, Double> userRatings = userItemMatrix.get(user);
                if (userRatings.containsKey(item1) && userRatings.containsKey(item2)) {
                    double rating1 = userRatings.get(item1);
                    double rating2 = userRatings.get(item2);
                    
                    dotProduct += rating1 * rating2;
                    norm1 += rating1 * rating1;
                    norm2 += rating2 * rating2;
                }
            }
        }
        
        if (norm1 == 0.0 || norm2 == 0.0) {
            return 0.0;
        }
        
        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }
    
    /**
     * 计算余弦相似度（用户间）
     */
    private double calculateUserCosineSimilarity(Map<String, Double> user1Ratings, Map<String, Double> user2Ratings) {
        Set<String> commonItems = new HashSet<>(user1Ratings.keySet());
        commonItems.retainAll(user2Ratings.keySet());
        
        if (commonItems.isEmpty()) {
            return 0.0;
        }
        
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;
        
        for (String item : commonItems) {
            double rating1 = user1Ratings.get(item);
            double rating2 = user2Ratings.get(item);
            
            dotProduct += rating1 * rating2;
            norm1 += rating1 * rating1;
            norm2 += rating2 * rating2;
        }
        
        if (norm1 == 0.0 || norm2 == 0.0) {
            return 0.0;
        }
        
        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }
    
    /**
     * 从数据库加载用户-物品评分矩阵
     */
    private Map<String, Map<String, Double>> loadUserItemMatrix() {
        Map<String, Map<String, Double>> matrix = new HashMap<>();
        
        String sql = "SELECT user_id, item_id, rating FROM user_item_ratings";
        
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql);
             ResultSet rs = stmt.executeQuery()) {
            
            while (rs.next()) {
                String userId = rs.getString("user_id");
                String itemId = rs.getString("item_id");
                double rating = rs.getDouble("rating");
                
                matrix.computeIfAbsent(userId, k -> new HashMap<>()).put(itemId, rating);
            }
            
        } catch (SQLException e) {
            System.err.println("加载用户-物品矩阵失败: " + e.getMessage());
            e.printStackTrace();
        }
        
        return matrix;
    }
    
    /**
     * 保存推荐结果到数据库
     */
    public void saveRecommendations() {
        try {
            // 获取所有物品和用户
            Set<String> items = getAllItems();
            Set<String> users = getAllUsers();
            
            // 生成基于物品的推荐
            for (String item : items) {
                List<RecommendationItem> recommendations = itemBasedRecommendation(item, 10);
                saveItemRecommendations(item, recommendations);
            }
            
            // 生成基于用户的推荐
            for (String user : users) {
                List<RecommendationItem> recommendations = userBasedRecommendation(user, 10);
                saveUserRecommendations(user, recommendations);
            }
            
            System.out.println("推荐结果已保存到数据库");
            
        } catch (Exception e) {
            System.err.println("保存推荐结果失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    private Set<String> getAllItems() {
        Set<String> items = new HashSet<>();
        String sql = "SELECT DISTINCT item_id FROM user_item_ratings";
        
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql);
             ResultSet rs = stmt.executeQuery()) {
            
            while (rs.next()) {
                items.add(rs.getString("item_id"));
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
        return items;
    }
    
    private Set<String> getAllUsers() {
        Set<String> users = new HashSet<>();
        String sql = "SELECT DISTINCT user_id FROM user_item_ratings";
        
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql);
             ResultSet rs = stmt.executeQuery()) {
            
            while (rs.next()) {
                users.add(rs.getString("user_id"));
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
        return users;
    }
    
    private void saveItemRecommendations(String itemId, List<RecommendationItem> recommendations) {
        String sql = "INSERT OR REPLACE INTO item_recommendations (item_id, recommended_items) VALUES (?, ?)";
        
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            
            String json = objectMapper.writeValueAsString(recommendations);
            stmt.setString(1, itemId);
            stmt.setString(2, json);
            stmt.executeUpdate();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private void saveUserRecommendations(String userId, List<RecommendationItem> recommendations) {
        String sql = "INSERT OR REPLACE INTO user_recommendations (user_id, recommended_items) VALUES (?, ?)";
        
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            
            String json = objectMapper.writeValueAsString(recommendations);
            stmt.setString(1, userId);
            stmt.setString(2, json);
            stmt.executeUpdate();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 推荐项数据类
     */
    public static class RecommendationItem {
        private String itemId;
        private double score;
        private String method;
        
        public RecommendationItem() {}
        
        public RecommendationItem(String itemId, double score, String method) {
            this.itemId = itemId;
            this.score = score;
            this.method = method;
        }
        
        // Getters and Setters
        public String getItemId() { return itemId; }
        public void setItemId(String itemId) { this.itemId = itemId; }
        
        public double getScore() { return score; }
        public void setScore(double score) { this.score = score; }
        
        public String getMethod() { return method; }
        public void setMethod(String method) { this.method = method; }
    }
} 